package com.matt.controller.web;

import com.dotflat.cc.po.ChildrenImportPO;
import com.dotflat.dashboard.service.DashboardService;
import com.dotflat.dashboard.vo.ChildrenCheckVO;
import com.dotflat.dashboard.vo.ChildrenClassCheckVO;
import com.dotflat.staffExtend.domain.StaffExtendCareer;
import com.dotflat.util.excelUtil.ExcelDataVo;
import com.dotflat.util.excelUtil.ExcelUtil;
import com.dotflat.util.excelUtil.PackExcelSheetsDataUtil;
import com.dotflat.util.excelUtil.StringUtils;
import com.matt.commons.dto.sso.StaffDTO;
import com.matt.commons.exception.BusinessException;
import com.matt.commons.model.StatusResponse;
import com.matt.domain.ChildrenNurseryInfoRecord;
import com.matt.domain.Classes;
import com.matt.domain.rel.RelClassesNurse;
import com.matt.domain.rel.RelClassesTeacher;
import com.matt.domain.vo.ContractVo;
import com.matt.foundation.DO.TeacherDO;
import com.matt.foundation.constant.PermissionConstant;
import com.matt.foundation.ext.aliyun.OSSProcessService;
import com.matt.foundation.model.ErrorCode;
import com.matt.foundation.model.MemVerificationCache;
import com.matt.foundation.query.ClassesQuery;
import com.matt.foundation.utils.DateUtil;
import com.matt.foundation.utils.annotation.RequiredPermissions;
import com.matt.foundation.vo.ClassesChildrenListVo;
import com.matt.foundation.vo.WebClassesPageVo;
import com.matt.foundation.vo.children.BabyNClassInfoVO;
import com.matt.foundation.vo.classes.ClassesVo;
import com.matt.repository.rel.RelClassesNurseRepository;
import com.matt.repository.rel.RelClassesTeacherRepository;
import com.matt.repository.rel.RelClassesTeachingAreaRepository;
import com.matt.service.ChildrenService;
import com.matt.service.ClassesService;
import com.matt.service.NurseryInfoRecordService;
import com.matt.service.StaffActionLogService;
import com.matt.service.remoteCall.SsoCallService;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

import static com.matt.foundation.constant.PermissionConstant.TEACH_CLASSES_FULL;
import static com.matt.foundation.constant.RequestConstant.TICKET;

/**
 * *******************************
 * Created by Ron.
 * form com.matt
 * on 2017/7/6
 * *******************************
 */
@RestController
@RequestMapping("/web/classes")
@Slf4j
public class WebClassesController {

    @Autowired
    private ClassesService classesService;

    @Autowired
    private ChildrenService childrenService;

    @Autowired
    private MemVerificationCache cache;

    @Autowired
    private SsoCallService ssoCallService;

    @Autowired
    private StaffActionLogService staffActionLogService;

    @Autowired
    private RelClassesTeacherRepository relClassesTeacherRepository;

    @Autowired
    private RelClassesTeachingAreaRepository relClassesTeachingAreaRepository;

    @Autowired
    private RelClassesNurseRepository relClassesNurseRepository;

    @Autowired
    private DashboardService dashboardService;

    @Autowired
    private OSSProcessService ossProcessService;

    @Autowired
    private  NurseryInfoRecordService nurseryInfoRecordService;

    @Value("${aliyun.oss.fileServerPrefix}")
    private String IMG_SERVICE_PREFIX;

    @ApiOperation(value = "我的任课班级",httpMethod = "POST")
    @RequestMapping("/my_list")

    public StatusResponse my_list(@RequestHeader(TICKET) String ticket, ClassesQuery classesQuery) {
        StaffDTO baseInfo = cache.getStaff(ticket);

        String roleCode = null;
        if (ssoCallService.checkStaffPermission(ticket, new String[]{PermissionConstant.TEACH_CLASSES_MYLIST})){
            roleCode = PermissionConstant.Role.TEACHER.codeOf();
        }else if (ssoCallService.checkStaffPermission(ticket, new String[]{PermissionConstant.TEACH_CLASSES_DOCTOR})){
            roleCode = PermissionConstant.Role.DOCTOR.codeOf();
        }else{
            throw new BusinessException(ErrorCode.ROLE_NOT_EXIST, "没有权限");
        }

        Page<WebClassesPageVo> classesPage = classesService.getClassesByStaff(baseInfo, roleCode, classesQuery);

        return StatusResponse.success(classesPage);
    }

//    @ApiOperation(value = "我的班级",httpMethod = "POST")
//    @RequestMapping("/doctor_list")
//    @RequiredPermissions("teach::classes::doctor_list")
//    public StatusResponse doctor_list(@RequestHeader(TICKET) String ticket, ClassesQuery classesQuery) {
//        StaffDTO baseInfo = cache.getStaff(ticket);
//        Page<WebClassesPageVo> classesPage = classesService.getClassesByStaff(baseInfo, PermissionConstant.Role.DOCTOR.codeOf() , classesQuery);
//        return StatusResponse.success(classesPage);
//    }

    @ApiOperation(value = "所有班级，园长用",httpMethod = "POST")
    @RequestMapping("/full_list")
    @RequiredPermissions(TEACH_CLASSES_FULL)
    public StatusResponse full_list(@RequestHeader(TICKET) String ticket, ClassesQuery classesQuery) {
        StaffDTO baseInfo = cache.getStaff(ticket);
        Page<WebClassesPageVo> classesPage = classesService.getClassesByStaff(baseInfo, PermissionConstant.Role.GARDEN_LEADER.codeOf() , classesQuery);
        return StatusResponse.success(classesPage);
    }

    @ApiOperation(value = "WEB 班级列表",httpMethod = "POST")
    @RequestMapping("/list")
    public StatusResponse list(@RequestHeader(TICKET) String ticket, ClassesQuery classesQuery) {
        StaffDTO baseInfo = cache.getStaff(ticket);
        classesQuery.setAreaId(baseInfo.getAreaId());
        classesQuery.setDate(DateUtil.millisToDateTime(DateUtil.startOfDayOfMilli(System.currentTimeMillis()), "yyyy-MM-dd"));
        Page<WebClassesPageVo> classesPage = classesService.getClassesByConditions(classesQuery);
        return StatusResponse.success(classesPage);
    }

    @ApiOperation(value = "班级列表",httpMethod = "POST")
    @RequestMapping("/list_by_area")
    public StatusResponse listByArea(@RequestHeader(TICKET) String ticket, Integer category) {
        StaffDTO baseInfo = cache.getStaff(ticket);
        String areaId = baseInfo.getAreaId();
        List<ClassesVo> classesList = classesService.getClassesByAreaAndCategory(areaId, category);
        return StatusResponse.success(classesList);
    }

    @ApiOperation(value = "WEB 班级教案列表",httpMethod = "POST")
    @RequestMapping("/plan")
    public StatusResponse plan(@RequestHeader(TICKET) String ticket, String classesId, String date) {
        Map response = classesService.getPlanByClasses(classesId, date, ticket, cache.getStaffId(ticket));
        return StatusResponse.success(response);
    }

    @ApiOperation(value = "WEB 根据天数查课程",httpMethod = "POST")
    @RequestMapping("/plan_by_day")
    public StatusResponse plan(@RequestHeader(TICKET) String ticket, Long day) {
        StaffDTO baseInfo = cache.getStaff(ticket);
        Map response = classesService.getPlanByDay(day, ticket);
        return StatusResponse.success(response);
    }

    @ApiOperation(value = "班级成员",httpMethod = "POST")
    @RequestMapping("/members")
    public StatusResponse members(@RequestHeader(TICKET) String ticket, String classesId) {
        StaffDTO baseInfo = cache.getStaff(ticket);
        Map response = classesService.getMemberByClassesId(classesId);
        return StatusResponse.success(response);
    }

    @ApiOperation(value = "班级内的小朋友",httpMethod = "GET")
    @GetMapping("/children")
    public StatusResponse children(@RequestHeader(TICKET) String ticket, String classesId) {
        StaffDTO baseInfo = cache.getStaff(ticket);

        Classes classes = classesService.getClassesById(baseInfo.getAreaId(), classesId);
        if (classes == null){
            throw new BusinessException(ErrorCode.CLASSES_NOT_EXIST, "班级不存在");
        }

        List<ClassesChildrenListVo> ret = childrenService.getChildrenByClasses(classes.getId());

        return StatusResponse.success(ret);
    }

    @ApiOperation(value = "WEB 创建班级",httpMethod = "POST")
    @RequestMapping(value = "/create", method = RequestMethod.POST)
    @ResponseBody
    public StatusResponse create(@RequestHeader(TICKET) String ticket, String classId, String[] childrenIds, String[] teacherIds, String[] nurseIds,String[] doctorIds,
                                 String name, String[] teachingAreaIds, String remark, Integer limitNum, String targetObjectId,
                                 String inChargeTeacherId, Integer category, Integer beginMonth, Integer endMonth) {

        StaffDTO staff = ssoCallService.getStaffDTO(ticket);
        StaffDTO inChargeTeacher = ssoCallService.getStaffDTO(ticket, inChargeTeacherId);

        String areaId = staff.getAreaId();
        StaffExtendCareer career;
        //班级信息
        Classes classesInfo =  classesService.getClassesById(staff.getAreaId(),classId);
        if (null == classesInfo){
            classesInfo = new Classes();
            classesInfo.setInChargeTeacherId("");
        }
        List<StaffExtendCareer> logList = new ArrayList<>();
        if (inChargeTeacherId != null) {
            List<String> stringList = new ArrayList<>();
            //添加当前班主任
            stringList.add(classesInfo.getInChargeTeacherId());

            String[]  teacherIds_v1 =  compare2(inChargeTeacherId.split(","), stringList.toArray(new String[stringList.size()]));
            for (int i = 0; i < teacherIds_v1.length; i++) {
                boolean contains = Arrays.asList(inChargeTeacherId).contains(teacherIds_v1[i]);
                if (contains){
                    career = StaffExtendCareer.of(areaId,staff.getId(),teacherIds_v1[i],"任"+name+"班主任");
                    career.setEndDate(null);
                    career.setReserved(classId);
                    logList.add(career);
                }else {
                    StaffExtendCareer staffExtendCareer = staffActionLogService.findByAreaIdAndReserved(areaId,classId);
                    if (null != staffExtendCareer){
                        staffExtendCareer.setEndDate(new Date());
                        logList.add(staffExtendCareer);
                    }
                    logList.add(StaffExtendCareer.of(areaId,staff.getId(),teacherIds_v1[i],name+"班主任离职"));
                }
            }
        }
        if (teacherIds != null) {
            List<RelClassesTeacher>  relClassesTeachers =  relClassesTeacherRepository.findByClassesId(classId);
            List<String> stringList = relClassesTeachers.stream().map(k->k.getStaffId()).collect(Collectors.toList());
            //获取当前的班主任
            String[] cinChargeTeacherId = new String[]{classesInfo.getInChargeTeacherId()};
            //去掉班主任后剩下的老师
            String[] teacherIds_v2 =  compare2(cinChargeTeacherId, stringList.toArray(new String[stringList.size()]));

            String[]  teacherIds_v1 =  compare2(teacherIds, teacherIds_v2);

            for (int i = 0; i < teacherIds_v1.length; i++) {
                boolean contains = Arrays.asList(teacherIds).contains(teacherIds_v1[i]);
                if (contains){
                    career = StaffExtendCareer.of(areaId,staff.getId(),teacherIds_v1[i],"任"+name+"老师");
                    career.setEndDate(null);
                    career.setReserved(classId);
                    logList.add(career);
                }else {
                    StaffExtendCareer staffExtendCareer = staffActionLogService.findByAreaIdAndReserved(areaId,classId);
                    if (null != staffExtendCareer){
                        staffExtendCareer.setEndDate(new Date());
                        logList.add(staffExtendCareer);
                    }
                    logList.add(StaffExtendCareer.of(areaId,staff.getId(),teacherIds_v1[i],name+"老师离职"));
                }
            }
        }
        if (nurseIds != null) {
            List<RelClassesNurse>  relClassesNurses = relClassesNurseRepository.findByIdLinkId(classId);
            List<String> stringList = relClassesNurses.stream().map(k->k.getId().getDomainId()).collect(Collectors.toList());
            String[]  teacherIds_v1 =  compare2(nurseIds, stringList.toArray(new String[stringList.size()]));
            for (int i = 0; i < teacherIds_v1.length; i++) {
                boolean contains = Arrays.asList(nurseIds).contains(teacherIds_v1[i]);
                if (contains){
                    career = StaffExtendCareer.of(areaId,staff.getId(),teacherIds_v1[i],"任"+name+"儿保医生");
                    career.setEndDate(null);
                    career.setReserved(classId);
                    logList.add(career);
                }else {
                    StaffExtendCareer staffExtendCareer = staffActionLogService.findByAreaIdAndReserved(areaId,classId);
                    if (null != staffExtendCareer){
                        staffExtendCareer.setEndDate(new Date());
                        logList.add(staffExtendCareer);
                    }
                    logList.add(StaffExtendCareer.of(areaId,staff.getId(),teacherIds_v1[i],name+"儿保医生离职"));
                }
            }

        }
        if (logList.size()>0){
            staffActionLogService.saveList(logList);
        }

        Classes classes = classesService.createClasses(classId, name, staff.getAreaId(), childrenIds, teacherIds, nurseIds,doctorIds,
                teachingAreaIds, remark, limitNum, targetObjectId,
                inChargeTeacher, category, beginMonth, endMonth);
        return StatusResponse.success(classes);
    }

    @ApiOperation(value = "WEB 修改班级",httpMethod = "POST")
    @RequestMapping(value = "/edit", method = RequestMethod.POST)
    @ResponseBody
    public StatusResponse edit(@RequestHeader(TICKET) String ticket, String classesId, String[] childrenIds,
                               String[] teacherIds, String[] nurseIds, String name, String[] teachingAreaIds, String remark,
                               Integer limitNum, String targetObjectId, String inChargeTeacherId, Integer category, Integer beginMonth, Integer endMonth) {

        StaffDTO staff = ssoCallService.getStaffDTO(ticket);

        StaffDTO inChargeTeacher = ssoCallService.getStaffDTO(ticket, inChargeTeacherId);

        Classes classes = classesService.modifyClasses(classesId, name, staff.getAreaId(), childrenIds, teacherIds, nurseIds, teachingAreaIds, remark,
                limitNum, targetObjectId, inChargeTeacher, category, beginMonth, endMonth);


        return StatusResponse.success(classes);
    }

    @ApiOperation(value = "班级详情",httpMethod = "POST")
    @RequestMapping("/detail")
    @ResponseBody
    public StatusResponse detail(@RequestHeader(TICKET) String ticket, String classesId) {
        StaffDTO staff = ssoCallService.getStaffDTO(ticket);
        Map response = classesService.getDetailByClassesId(classesId);
        return StatusResponse.success(response);
    }


    @ApiOperation(value = "班级详情",httpMethod = "POST")
    @PostMapping("/teacherDetail")
    @ResponseBody
    public StatusResponse teacherDetail(@RequestBody TeacherDO teacherDO) {
        List response = classesService.getDetailByClassesIdList(teacherDO);
        return StatusResponse.success(response);
    }


    @ApiOperation(value = "WEB 禁用班级",httpMethod = "POST")
    @RequestMapping("/disable")
    @ResponseBody
    public StatusResponse disable(@RequestHeader(TICKET) String ticket, String classesId) {
        Boolean result = classesService.disable(classesId);
        return StatusResponse.success(result);
    }

    @ApiOperation(value = "WEB 启用班级",httpMethod = "POST")
    @RequestMapping("/enable")
    @ResponseBody
    public StatusResponse enable(@RequestHeader(TICKET) String ticket, String classesId) {
        Boolean result = classesService.enable(classesId);
        return StatusResponse.success(result);
    }

    @ApiOperation(value = "WEB 获取班级",httpMethod = "POST")
    @RequestMapping(value = "/get", method = RequestMethod.POST)
    @ResponseBody
    public StatusResponse get(@RequestHeader(TICKET) String ticket, String classesId) {
        Map response = classesService.getClassesById(classesId);
        return StatusResponse.success(response);
    }

    @ApiOperation(value = "WEB 排课",httpMethod = "POST")
    @RequestMapping(value = "/course_schedule", method = RequestMethod.POST)
    @ResponseBody
    public StatusResponse course_schedule(@RequestHeader(TICKET) String ticket, String classesId, String baseTime) {
        try {
            StaffDTO staff = ssoCallService.getStaffDTO(ticket);
            String areaId = staff.getAreaId();

            Classes classes = classesService.scheduled(classesId, baseTime);
            return StatusResponse.success(classes);
        } catch (Exception e) {
            return StatusResponse.error(ErrorCode.COURSE_SCHEDULE_ERROR, "排课失败");
        }
    }

    //查询二个集合中不相同的数据
    public static String[] compare2(String[] arr1, String[] arr2) {
        Set<String> set1 = new HashSet<>(Arrays.asList(arr1));
        Set<String> set2 = new HashSet<>(Arrays.asList(arr2));
        Set<String> set3 = new HashSet<>(set2);

        set3.addAll(set1);//set3 [100, 1, 2, 5, 7, -1]; set集合有去重特性
        set1.retainAll(set2);//retainAll():保留包含在指定 collection 中的元素；s1：[-1,1,2]
        set3.removeAll(set1);//	removeAll(); 移除 set 中那些包含在指定 collection 中的元素 ; s3 [3,4,100,5,7,-12,12]
        return set3.toArray(new String[set3.size()]);
    }


    // 2025年法定节假日列表（格式：yyyy-MM-dd）
    private static final Set<String> HOLIDAYS_2025 = new HashSet<String>() {{
        // 元旦：2025年1月1日（周三）
        add("2025-01-01");

        // 春节：2025年1月29日-2月2日（正月初一至初五，除夕1月28日可能调休）
        add("2025-01-29");
        add("2025-01-30");
        add("2025-01-31");
        add("2025-02-01");
        add("2025-02-02");

        // 清明节：2025年4月4日（周五）
        add("2025-04-04");

        // 劳动节：2025年5月1日-5月3日（周四至周六）
        add("2025-05-01");
        add("2025-05-02");
        add("2025-05-03");




        // 国庆节：2025年10月1日-10月7日（周三至下周二）
        add("2025-10-01");
        add("2025-10-02");
        add("2025-10-03");
        add("2025-10-04");
        add("2025-10-05");
        add("2025-10-06");
        add("2025-10-07");
    }};
    /**
     * 班级考勤
     */
    @PostMapping(value = "/classChildrenCheckList")
    @ResponseBody
    @ApiOperation(value = "班级 - 考勤")
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "header", name = "ticket", value = "票据", required = true, dataType = "string"),
            @ApiImplicitParam(paramType = "query", name = "classId", value = "班级ID", required = true, dataType = "string"),
            @ApiImplicitParam(paramType = "query", name = "name", value = "学生名字", required = false, dataType = "string"),
            @ApiImplicitParam(paramType = "query", name = "beginAt", value = "开始时间yyyy-MM-dd", required = true, dataType = "string"),
            @ApiImplicitParam(paramType = "query", name = "endAt", value = "结束时间yyyy-MM-dd", required = true, dataType = "string"),
    })
//    public StatusResponse classCheckList(@RequestHeader(TICKET) String ticket,
//                                         String classId, String name,
//                                         String beginAt, String endAt) {
//        // 1. 基础参数校验
//        if (StringUtils.isBlank(ticket)) {
//            return StatusResponse.error("99999", "ticket不能为空");
//        }
//        if (StringUtils.isBlank(beginAt) || StringUtils.isBlank(endAt)) {
//            return StatusResponse.error("99999", "开始日期和结束日期不能为空");
//        }
//
//        // 2. SSO获取用户信息
//        StaffDTO staffDTO = ssoCallService.getStaffDTO(ticket);
//        if (staffDTO == null || StringUtils.isBlank(staffDTO.getAreaId())) {
//            log.error("获取用户园区信息失败，ticket:{}", ticket);
//            return StatusResponse.error("99999", "用户身份无效，请重新登录");
//        }
//
//        // 3. 统一日期格式（支持yyyy-MM-dd和yyyy-MM-dd HH:mm:ss）
//        SimpleDateFormat fullSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//        SimpleDateFormat dateSdf = new SimpleDateFormat("yyyy-MM-dd");
//        fullSdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
//        dateSdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
//
//        Date beginDate = null;
//        Date endDate = null;
//        try {
//            beginDate = parseDate3(beginAt, fullSdf, dateSdf);
//            endDate = parseDate3(endAt, fullSdf, dateSdf);
//            beginAt = dateSdf.format(beginDate) + " 00:00:00";
//            endAt = dateSdf.format(endDate) + " 23:59:59";
//        } catch (ParseException e) {
//            log.error("日期格式解析失败，beginAt:{}, endAt:{}", beginAt, endAt, e);
//            return StatusResponse.error("99999", "日期格式错误，请传入yyyy-MM-dd或yyyy-MM-dd HH:mm:ss格式");
//        }
//
//        // 4. 获取班级学生列表
//        List<BabyNClassInfoVO> babyClassInfoList = childrenService.getChildrenClassInfoForCrm(
//                staffDTO.getAreaId(), name, classId);
//        if (CollectionUtils.isEmpty(babyClassInfoList)) {
//            return StatusResponse.success(Collections.emptyList());
//        }
//
//        // 5. 获取学生考勤记录（按学生ID+日期分组，保留【当天最后一条记录】）
//        List<String> childrenIds = babyClassInfoList.stream()
//                .map(BabyNClassInfoVO::getId)
//                .collect(Collectors.toList());
//        List<ChildrenCheckVO> childrenCheckList = dashboardService.getChildrenClassCheck(childrenIds, beginAt, endAt);
//
//        // 5.1 预处理：按“学生ID_日期”分组，保留【当天最后一条记录】（核心修复）
//        Map<String, ChildrenCheckVO> studentLastCheckMap = new HashMap<>();
//        for (ChildrenCheckVO check : childrenCheckList) {
//            if (check == null || StringUtils.isBlank(check.getChildrenId())) {
//                continue; // 过滤学生ID空的无效记录
//            }
//
//            // 解析打卡日期（统一为yyyy-MM-dd，用于分组）
//            String checkDateStr;
//            try {
//                // 关键：根据记录类型选日期字段（入园用checkInTime，出园用checkOutTime）
//                String timeField = (check.getStatus() == 2 || check.getStatus() == 6) ? check.getCheckOutTime() : check.getCheckInTime();
//                if (StringUtils.isBlank(timeField)) {
//                    log.warn("记录时间为空：childrenId={}, status={}", check.getChildrenId(), check.getStatus());
//                    continue;
//                }
//                Date checkDate = parseDate3(timeField, fullSdf, dateSdf);
//                checkDateStr = dateSdf.format(checkDate);
//            } catch (ParseException e) {
//                log.error("解析记录日期失败：childrenId={}, time={}", check.getChildrenId(), check.getCheckInTime(), e);
//                continue;
//            }
//
//            String key = check.getChildrenId() + "_" + checkDateStr;
//            ChildrenCheckVO existCheck = studentLastCheckMap.get(key);
//            if (existCheck == null) {
//                studentLastCheckMap.put(key, check);
//                log.info("新增记录：key={}, status={}, 时间={}", key, check.getStatus(),
//                        (check.getStatus() == 2 || check.getStatus() == 6) ? check.getCheckOutTime() : check.getCheckInTime());
//            } else {
//                // 比较时间：保留更晚的记录（核心修复：出园记录用checkOutTime）
//                try {
//                    // 原记录的时间字段（根据状态选）
//                    String existTimeField = (existCheck.getStatus() == 2 || existCheck.getStatus() == 6) ? existCheck.getCheckOutTime() : existCheck.getCheckInTime();
//                    // 当前记录的时间字段（根据状态选）
//                    String currentTimeField = (check.getStatus() == 2 || check.getStatus() == 6) ? check.getCheckOutTime() : check.getCheckInTime();
//
//                    if (StringUtils.isBlank(existTimeField) || StringUtils.isBlank(currentTimeField)) {
//                        log.warn("时间字段为空：exist={}, current={}", existTimeField, currentTimeField);
//                        continue;
//                    }
//
//                    Date existTime = parseDate3(existTimeField, fullSdf, dateSdf);
//                    Date currentTime = parseDate3(currentTimeField, fullSdf, dateSdf);
//
//                    // 时间相同：优先保留出园记录（status=2/6）
//                    if (currentTime.after(existTime)) {
//                        studentLastCheckMap.put(key, check);
//                        log.info("更新记录：key={}, 原status={}→新status={}", key, existCheck.getStatus(), check.getStatus());
//                    } else if (currentTime.equals(existTime)) {
//                        // 时间相同，出园记录（2/6）优先级高于入园记录（1/5）
//                        boolean existIsCheckOut = existCheck.getStatus() == 2 || existCheck.getStatus() == 6;
//                        boolean currentIsCheckOut = check.getStatus() == 2 || check.getStatus() == 6;
//                        if (currentIsCheckOut && !existIsCheckOut) {
//                            studentLastCheckMap.put(key, check);
//                            log.info("时间相同，保留出园记录：key={}, 原status={}→新status={}", key, existCheck.getStatus(), check.getStatus());
//                        }
//                    }
//                } catch (ParseException e) {
//                    log.error("比较时间失败：key={}", key, e);
//                }
//            }
//        }
//
//        // 6. 计算应出勤日（排除周末和节假日）
//        List<String> dateList = DateUtil.getDay(beginAt, endAt);
//        List<String> workDayList = new ArrayList<>();
//        for (String dateStr : dateList) {
//            try {
//                Date date = dateSdf.parse(dateStr);
//                Calendar cal = Calendar.getInstance();
//                cal.setTime(date);
//                int weekDay = cal.get(Calendar.DAY_OF_WEEK);
//                // 排除周六（7）、周日（1）和节假日
//                if (weekDay != Calendar.SUNDAY && weekDay != Calendar.SATURDAY
//                        && !HOLIDAYS_2025.contains(dateStr)) {
//                    workDayList.add(dateStr);
//                }
//            } catch (ParseException e) {
//                log.error("解析应出勤日失败：{}", dateStr, e);
//            }
//        }
//        int shouldAttendCount = workDayList.size();
//
//        // 7. 遍历学生统计考勤（关键修改：按最后一条记录判定状态+修正出勤逻辑）
//        List<Map<String, Object>> mapList = new ArrayList<>();
//        for (BabyNClassInfoVO baby : babyClassInfoList) {
//            Map<String, Object> studentMap = new HashMap<>();
//            studentMap.put("id", baby.getId());
//            studentMap.put("name", baby.getName());
//            studentMap.put("shouldAttendCount", shouldAttendCount); // 应出勤天数
//
//            int attend = 0;        // 出勤天数（只要入园就算：正常/迟到/早退）
//            int leave = 0;         // 事假天数
//            int sick = 0;          // 病假天数
//            int late = 0;          // 迟到天数
//            int earlyLeave = 0;    // 早退天数（修复：确保会增加）
//
//            // 遍历应出勤日，按“最后一条记录”判定状态
//            for (String workDay : workDayList) {
//                String key = baby.getId() + "_" + workDay;
//                ChildrenCheckVO lastCheck = studentLastCheckMap.get(key);
//                String dayResult = "缺勤"; // 默认：无任何打卡=缺勤
//
//                if (lastCheck != null) {
//                    int status = lastCheck.getStatus();
//                    // 核心规则1：只要不是请假（status=4），有入园就算出勤
//                    boolean isAttend = Arrays.asList(1, 2, 5, 6).contains(status);
//                    if (isAttend) {
//                        attend++; // 仅有效出勤状态才计数
//                    }
//                   // 在遍历workDay时添加日志，查看每个日期的状态和出勤计数依据
//                    log.info("日期：{}，学生ID：{}，状态：{}，是否算作出勤：{}",
//                            workDay, baby.getId(),
//                            lastCheck != null ? lastCheck.getStatus() : "无记录",
//                            isAttend);
//                    // 核心规则2：按最后一条记录设置状态文本+细分计数
//                    switch (status) {
//                        case 1: // 正常入园（无出园打卡）
//                            dayResult = "出勤"; // 无出园也算正常出勤
//                            break;
//                        case 2: // 正常出园（最后一条是出园）
//                            dayResult = "出勤"; // 前端显示“出园”
//                            break;
//                        case 5: // 迟到（最后一条是迟到，有无出园都算迟到）
//                            dayResult = "迟到";
//                            late++; // 迟到计数
//                            break;
//                        case 6: // 早退（最后一条是早退）
//                            dayResult = "早退";
//                            earlyLeave++; // 早退计数（修复：此处会正确增加）
//                            break;
//                        case 4: // 请假（需区分事假/病假）
//                            if (lastCheck.getHealthyStatus() == 1) {
//                                dayResult = "病假";
//                                sick++;
//                            } else {
//                                dayResult = "事假";
//                                leave++;
//                            }
//                            break;
//                        default: // 其他状态（如0=无效记录）
//                            dayResult = "缺勤";
//                            break;
//                    }
//                }
//
//                // 给前端返回当天的状态（如2025-08-26对应“早退”）
//                studentMap.put(workDay, dayResult);
//
//            }
//
//            // 核心规则3：计算缺勤天数（修正公式：应出勤 - 出勤 - 事假 - 病假）
//            // 说明：迟到/早退属于出勤的细分，无需从出勤中扣除
//            int lack = shouldAttendCount - attend - leave - sick;
//            lack = Math.max(lack, 0); // 防止负数（异常场景）
//
//            // 填充最终统计数据
//            studentMap.put("attendanceCount", attend);       // 出勤天数
//            studentMap.put("leaveCount", leave);             // 事假天数
//            studentMap.put("sickCount", sick);              // 病假天数
//            studentMap.put("chiCount", late);                // 迟到天数
//            studentMap.put("zaoCount", earlyLeave);          // 早退天数（修复：已正确统计）
//            studentMap.put("lackCount", lack);               // 缺勤天数
//
//            mapList.add(studentMap);
//        }
//
//        return StatusResponse.success(mapList);
//    }
//
//    // 辅助方法：统一解析日期（支持yyyy-MM-dd和yyyy-MM-dd HH:mm:ss）
//    private Date parseDate3(String dateStr, SimpleDateFormat fullSdf, SimpleDateFormat dateSdf) throws ParseException {
//        if (dateStr.contains(":")) {
//            return fullSdf.parse(dateStr); // 带时间格式
//        } else {
//            return dateSdf.parse(dateStr); // 纯日期格式
//        }
//    }

    public StatusResponse classCheckList(@RequestHeader(TICKET) String ticket,
                                         String classId, String name,
                                         String beginAt, String endAt) {
        // 1. 基础参数校验
        if (StringUtils.isBlank(ticket)) {
            return StatusResponse.error("99999", "ticket不能为空");
        }
        if (StringUtils.isBlank(beginAt) || StringUtils.isBlank(endAt)) {
            return StatusResponse.error("99999", "开始日期和结束日期不能为空");
        }

        //todo 25号新增
        if (StringUtils.isBlank(classId)) {
            classId="-1";
        }
        // 2. SSO获取用户信息
        StaffDTO staffDTO = ssoCallService.getStaffDTO(ticket);
        if (staffDTO == null || StringUtils.isBlank(staffDTO.getAreaId())) {
            log.error("获取用户园区信息失败，ticket:{}", ticket);
            return StatusResponse.error("99999", "用户身份无效，请重新登录");
        }
        // 3. 统一日期格式（支持yyyy-MM-dd和yyyy-MM-dd HH:mm:ss）
        SimpleDateFormat fullSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        SimpleDateFormat dateSdf = new SimpleDateFormat("yyyy-MM-dd");
        fullSdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
        dateSdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));

        Date beginDate = null;
        Date endDate = null;
        try {
            beginDate = parseDate3(beginAt, fullSdf, dateSdf);
            endDate = parseDate3(endAt, fullSdf, dateSdf);
            beginAt = dateSdf.format(beginDate) + " 00:00:00";
            endAt = dateSdf.format(endDate) + " 23:59:59";
        } catch (ParseException e) {
            log.error("日期格式解析失败，beginAt:{}, endAt:{}", beginAt, endAt, e);
            return StatusResponse.error("99999", "日期格式错误，请传入yyyy-MM-dd或yyyy-MM-dd HH:mm:ss格式");
        }

        // 4. 获取班级学生列表
        List<BabyNClassInfoVO> babyClassInfoList = childrenService.getChildrenClassInfoForCrm(
                staffDTO.getAreaId(), name, classId);
        if (CollectionUtils.isEmpty(babyClassInfoList)) {
            return StatusResponse.success(Collections.emptyList());
        }

        // 5. 获取学生考勤记录（按学生ID+日期分组，保留【当天最后一条记录】）
        List<String> childrenIds = babyClassInfoList.stream()
                .map(BabyNClassInfoVO::getId)
                .collect(Collectors.toList());
        List<ChildrenCheckVO> childrenCheckList = dashboardService.getChildrenClassCheck(childrenIds, beginAt, endAt);

        // 5.1 预处理：按“学生ID_日期”分组，保留【当天最后一条记录】（适配新状态字段）
        Map<String, ChildrenCheckVO> studentLastCheckMap = new HashMap<>();
        for (ChildrenCheckVO check : childrenCheckList) {
            if (check == null || StringUtils.isBlank(check.getChildrenId())) {
                continue; // 过滤学生ID空的无效记录
            }

            // 解析打卡日期（统一为yyyy-MM-dd，用于分组）
            String checkDateStr;
            try {
                // 关键：根据记录类型选日期字段（有出园用checkOutTime，否则用checkInTime）
                String timeField = check.getLeaveStatus() != 0 ? check.getCheckOutTime() : check.getCheckInTime();
                if (StringUtils.isBlank(timeField)) {
                    log.warn("记录时间为空：childrenId={}, status={}, leaveStatus={}",
                            check.getChildrenId(), check.getStatus(), check.getLeaveStatus());
                    continue;
                }
                Date checkDate = parseDate3(timeField, fullSdf, dateSdf);
                checkDateStr = dateSdf.format(checkDate);
            } catch (ParseException e) {
                log.error("解析记录日期失败：childrenId={}, checkInTime={}, checkOutTime={}",
                        check.getChildrenId(), check.getCheckInTime(), check.getCheckOutTime(), e);
                continue;
            }

            String key = check.getChildrenId() + "_" + checkDateStr;
            ChildrenCheckVO existCheck = studentLastCheckMap.get(key);
            if (existCheck == null) {
                studentLastCheckMap.put(key, check);
                log.info("新增记录：key={}, status={}, leaveStatus={}, 时间={}",
                        key, check.getStatus(), check.getLeaveStatus(),
                        check.getLeaveStatus() != 0 ? check.getCheckOutTime() : check.getCheckInTime());
            } else {
                // 比较时间：保留更晚的记录（优先用出园时间比较）
                try {
                    // 原记录的时间字段（有出园用出园时间）
                    String existTimeField = existCheck.getLeaveStatus() != 0 ? existCheck.getCheckOutTime() : existCheck.getCheckInTime();
                    // 当前记录的时间字段（有出园用出园时间）
                    String currentTimeField = check.getLeaveStatus() != 0 ? check.getCheckOutTime() : check.getCheckInTime();

                    if (StringUtils.isBlank(existTimeField) || StringUtils.isBlank(currentTimeField)) {
                        log.warn("时间字段为空：exist={}, current={}", existTimeField, currentTimeField);
                        continue;
                    }

                    Date existTime = parseDate3(existTimeField, fullSdf, dateSdf);
                    Date currentTime = parseDate3(currentTimeField, fullSdf, dateSdf);

                    // 时间相同：优先保留有出园记录的
                    if (currentTime.after(existTime)) {
                        studentLastCheckMap.put(key, check);
                        log.info("更新记录：key={}, 原状态({},{}), 新状态({},{}), 原时间={}, 新时间={}",
                                key, existCheck.getStatus(), existCheck.getLeaveStatus(),
                                check.getStatus(), check.getLeaveStatus(),
                                existTimeField, currentTimeField);
                    } else if (currentTime.equals(existTime)) {
                        // 时间相同，有出园记录的优先级高
                        boolean existHasLeave = existCheck.getLeaveStatus() != 0;
                        boolean currentHasLeave = check.getLeaveStatus() != 0;
                        if (currentHasLeave && !existHasLeave) {
                            studentLastCheckMap.put(key, check);
                            log.info("时间相同，保留出园记录：key={}, 原状态({},{}), 新状态({},{}),",
                                    key, existCheck.getStatus(), existCheck.getLeaveStatus(),
                                    check.getStatus(), check.getLeaveStatus());
                        }
                    }
                } catch (ParseException e) {
                    log.error("比较时间失败：key={}", key, e);
                }
            }
        }

        // 6. 计算应出勤日（排除周末和节假日）
        List<String> dateList = DateUtil.getDay(beginAt, endAt);
        List<String> workDayList = new ArrayList<>();
        for (String dateStr : dateList) {
            try {
                Date date = dateSdf.parse(dateStr);
                Calendar cal = Calendar.getInstance();
                cal.setTime(date);
                int weekDay = cal.get(Calendar.DAY_OF_WEEK);
                // 排除周六（7）、周日（1）和节假日
                if (weekDay != Calendar.SUNDAY && weekDay != Calendar.SATURDAY
                        && !HOLIDAYS_2025.contains(dateStr)) {
                    workDayList.add(dateStr);
                }
            } catch (ParseException e) {
                log.error("解析应出勤日失败：{}", dateStr, e);
            }
        }
        int shouldAttendCount = workDayList.size();

        // 7. 遍历学生统计考勤（适配新状态字段）
        List<Map<String, Object>> mapList = new ArrayList<>();
        for (BabyNClassInfoVO baby : babyClassInfoList) {
            Map<String, Object> studentMap = new HashMap<>();
            studentMap.put("id", baby.getId());
            studentMap.put("name", baby.getName());
            studentMap.put("relStatus", baby.getRelStatus());
            studentMap.put("shouldAttendCount", shouldAttendCount); // 应出勤天数

            int attend = 0;        // 出勤天数（只要入园就算）
            int leave = 0;         // 事假天数
            int sick = 0;          // 病假天数
            int late = 0;          // 迟到天数
            int earlyLeave = 0;    // 早退天数

            // 遍历应出勤日，按“最后一条记录”判定状态
            for (String workDay : workDayList) {
                String key = baby.getId() + "_" + workDay;
                ChildrenCheckVO lastCheck = studentLastCheckMap.get(key);
                String dayResult = "缺勤"; // 默认：无任何打卡=缺勤

                if (lastCheck != null) {
                    int status = lastCheck.getStatus();          // 入园状态
                    int leaveStatus = lastCheck.getLeaveStatus(); // 离园状态

                    // 核心规则1：只要不是请假（status=4），有入园就算出勤
                    boolean isAttend = Arrays.asList(1, 3, 5).contains(status) && status != 4;
                    if (isAttend) {
                        attend++; // 有效出勤计数
                    }

                    log.info("日期：{}，学生ID：{}，入园状态：{}，离园状态：{}，是否算作出勤：{}",
                            workDay, baby.getId(), status, leaveStatus, isAttend);

                    // 核心规则2：按状态组合设置显示文本和细分计数
                    if (status == 4) {
                        // 请假状态（忽略离园状态）
                        if (lastCheck.getHealthyStatus() == 1) {
                            dayResult = "病假";
                            sick++;
                        } else {
                            dayResult = "事假";
                            leave++;
                        }
                    } else if (status == 3) {
                        // 拒绝入园
                        dayResult = "拒绝入园";
                    } else if (status == 5) {
                        // 迟到（无论是否出园）
                        dayResult = leaveStatus != 0 ? "迟到+出园" : "迟到";
                        late++;
                    } else if (status == 1) {
                        // 正常入园
                        if (leaveStatus == 2) {
                            // 正常出园
                            dayResult = "正常出勤";
                        } else if (leaveStatus == 6) {
                            // 早退
                            dayResult = "早退";
                            earlyLeave++;
                        } else {
                            // 未出园
                            dayResult = "已入园";
                        }
                    }
                }

                // 给前端返回当天的状态（如2025-08-26对应“早退”）
                studentMap.put(workDay, dayResult);
            }

            // 核心规则3：计算缺勤天数（应出勤 - 出勤 - 事假 - 病假）
            int lack = shouldAttendCount - attend - leave - sick;
            lack = Math.max(lack, 0); // 防止负数

            // 填充最终统计数据
            studentMap.put("attendanceCount", attend);       // 出勤天数
            studentMap.put("leaveCount", leave);             // 事假天数
            studentMap.put("sickCount", sick);              // 病假天数
            studentMap.put("chiCount", late);                // 迟到天数
            studentMap.put("zaoCount", earlyLeave);          // 早退天数
            studentMap.put("lackCount", lack);               // 缺勤天数

            mapList.add(studentMap);
        }

        return StatusResponse.success(mapList);
    }

    // 辅助方法：统一解析日期（支持yyyy-MM-dd和yyyy-MM-dd HH:mm:ss）
    private Date parseDate3(String dateStr, SimpleDateFormat fullSdf, SimpleDateFormat dateSdf) throws ParseException {
        if (dateStr.contains(":")) {
            return fullSdf.parse(dateStr); // 带时间格式
        } else {
            return dateSdf.parse(dateStr); // 纯日期格式
        }
    }


    // ------------------------------ 新增通用日期解析方法 ------------------------------
    /**
     * 通用日期解析：先试带时间格式，失败再试纯日期格式
     * @param dateStr 待解析的日期字符串
     * @param fullSdf 带时间的格式化对象（yyyy-MM-dd HH:mm:ss）
     * @param dateSdf 纯日期的格式化对象（yyyy-MM-dd）
     * @return 解析后的Date对象
     * @throws ParseException 两种格式都解析失败时抛出
     */
    private Date parseDate(String dateStr, SimpleDateFormat fullSdf, SimpleDateFormat dateSdf) throws ParseException {
        if (StringUtils.isBlank(dateStr)) {
            throw new ParseException("日期字符串为空", 0);
        }
        try {
            // 先尝试解析带时间的格式（如2025-08-21 08:30:00）
            return fullSdf.parse(dateStr);
        } catch (ParseException e) {
            // 带时间格式解析失败，尝试纯日期格式（如2025-08-21）
            try {
                return dateSdf.parse(dateStr);
            } catch (ParseException ex) {
                // 两种格式都失败，抛出最终异常
                throw new ParseException("无法解析日期：" + dateStr + "，支持格式：yyyy-MM-dd 或 yyyy-MM-dd HH:mm:ss", 0);
            }
        }
    }

    // ------------------------------ 修复辅助方法：适配两种日期格式 ------------------------------
    /**
     * 辅助方法：获取请假记录的健康状态（适配纯日期/带时间格式）
     */
    private ChildrenCheckVO getLeaveCheckVO(List<ChildrenCheckVO> checkList, String childrenId,
                                            String workDay, SimpleDateFormat fullSdf, SimpleDateFormat dateSdf) {
        for (ChildrenCheckVO check : checkList) {
            if (check == null
                    || !StringUtils.equals(check.getChildrenId(), childrenId)
                    || check.getStatus() != 4
                    || check.getCheckInTime() == null) {
                continue;
            }
            try {
                // 用通用解析方法处理checkInTime
                Date checkInDate = parseDate(check.getCheckInTime(), fullSdf, dateSdf);
                String checkDateStr = dateSdf.format(checkInDate);
                // 匹配当天的请假记录
                if (StringUtils.equals(workDay, checkDateStr)) {
                    return check;
                }
            } catch (ParseException e) {
                log.error("获取请假健康状态失败，checkInTime:{}", check.getCheckInTime(), e);
            }
        }
        return null;
    }

    /**
     * 状态优先级：数字越大优先级越高（确保当天保留最关键的状态）
     */
    private int getStatusPriority(Integer status) {
        switch (status) {
            case 6: return 5; // 早退
            case 2: return 4; // 正常出园
            case 5: return 3; // 迟到
            case 1: return 2; // 正常入园
            case 4: return 1; // 请假
            default: return 0; // 缺勤
        }
    }



    // 注：需定义dateSdf为类成员变量，避免重复创建
    private final SimpleDateFormat dateSdf = new SimpleDateFormat("yyyy-MM-dd");



    /**
     * 多个导出sheet
     */
//    @PostMapping(value = "/exportClassChildrenCheckList")
//    @ResponseBody
//    @ApiOperation(value = "导出 - 班级考勤")
//    @ApiImplicitParams({
//            @ApiImplicitParam(paramType = "header", name = "ticket", value = "票据", required = true, dataType = "string"),
//            @ApiImplicitParam(paramType = "query", name = "classId", value = "班级ID", required = true, dataType = "string"),
//            @ApiImplicitParam(paramType = "query", name = "name", value = "学生名字", required = false, dataType = "string"),
//            @ApiImplicitParam(paramType = "query", name = "beginAt", value = "开始时间yyyy-MM-dd", required = true, dataType = "string"),
//            @ApiImplicitParam(paramType = "query", name = "endAt", value = "结束时间yyyy-MM-dd", required = true, dataType = "string"),
//    })
//    public StatusResponse exportClassChildrenCheckList(HttpServletResponse response,
//                                                       @RequestHeader(TICKET) String ticket,
//                                                       String classId, String name,
//                                                       String beginAt, String endAt) {
//        // 1. 基础参数校验
//        if (StringUtils.isBlank(ticket)) {
//            return StatusResponse.error("99999", "ticket不能为空");
//        }
//        if (StringUtils.isBlank(classId)) {
//            return StatusResponse.error("99999", "班级ID不能为空");
//        }
//        if (StringUtils.isBlank(beginAt) || StringUtils.isBlank(endAt)) {
//            return StatusResponse.error("99999", "开始日期和结束日期不能为空");
//        }
//
//        // 2. SSO获取用户信息
//        StaffDTO staffDTO = ssoCallService.getStaffDTO(ticket);
//        if (staffDTO == null || StringUtils.isBlank(staffDTO.getAreaId())) {
//            log.error("获取用户园区信息失败，ticket:{}", ticket);
//            return StatusResponse.error("99999", "用户身份无效，请重新登录");
//        }
//
//        // 3. 查询班级信息
//        Classes classes = classesService.selectClassById(classId);
//        if (classes == null || StringUtils.isBlank(classes.getName())) {
//            log.error("班级不存在或班级名称为空，classId:{}", classId);
//            return StatusResponse.error("99999", "无效的班级ID");
//        }
//
//        // 4. 统一日期格式（与查询接口完全一致，复用parseDate3）
//        SimpleDateFormat fullSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//        SimpleDateFormat dateSdf = new SimpleDateFormat("yyyy-MM-dd");
//        SimpleDateFormat yearMonthDaySdf = new SimpleDateFormat("yyyy年MM月dd日");
//        fullSdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
//        dateSdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
//        yearMonthDaySdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
//
//        Date beginDate = null;
//        Date endDate = null;
//        try {
//            beginDate = parseDate3(beginAt, fullSdf, dateSdf); // 复用查询接口的日期解析方法
//            endDate = parseDate3(endAt, fullSdf, dateSdf);
//            beginAt = dateSdf.format(beginDate) + " 00:00:00";
//            endAt = dateSdf.format(endDate) + " 23:59:59";
//        } catch (ParseException e) {
//            log.error("日期格式解析失败，beginAt:{}, endAt:{}", beginAt, endAt, e);
//            return StatusResponse.error("99999", "日期格式错误，请传入yyyy-MM-dd或yyyy-MM-dd HH:mm:ss格式");
//        }
//
//        // 5. 获取班级学生列表
//        List<BabyNClassInfoVO> babyClassInfoList = childrenService.getChildrenClassInfoForCrm(
//                staffDTO.getAreaId(), name, classId);
//        if (CollectionUtils.isEmpty(babyClassInfoList)) {
//            log.info("班级无学生数据，classId:{}, areaId:{}", classId, staffDTO.getAreaId());
//            return StatusResponse.success("班级无学生数据，无需导出");
//        }
//
//        // 6. 获取考勤记录 + 预处理（与查询接口完全同步：保留当天最后一条记录）
//        List<String> childrenIds = babyClassInfoList.stream()
//                .map(BabyNClassInfoVO::getId)
//                .collect(Collectors.toList());
//        List<ChildrenCheckVO> childrenCheckList = dashboardService.getChildrenClassCheck(childrenIds, beginAt, endAt);
//        log.info("查询到的考勤记录共{}条", childrenCheckList.size());
//
//        // 6.1 预处理：按“学生ID_日期”分组，保留【当天最后一条记录】（完全复用查询接口逻辑）
//        Map<String, ChildrenCheckVO> studentLastCheckMap = new HashMap<>();
//        for (ChildrenCheckVO check : childrenCheckList) {
//            if (check == null || StringUtils.isBlank(check.getChildrenId())) {
//                continue;
//            }
//
//            // 解析打卡日期（统一为yyyy-MM-dd，用于分组）
//            String checkDateStr;
//            try {
//                // 关键：入园用checkInTime，出园用checkOutTime（与查询接口一致）
//                String timeField = (check.getStatus() == 2 || check.getStatus() == 6) ? check.getCheckOutTime() : check.getCheckInTime();
//                if (StringUtils.isBlank(timeField)) {
//                    log.warn("记录时间为空：childrenId={}, status={}", check.getChildrenId(), check.getStatus());
//                    continue;
//                }
//                Date checkDate = parseDate3(timeField, fullSdf, dateSdf);
//                checkDateStr = dateSdf.format(checkDate);
//            } catch (ParseException e) {
//                log.error("解析记录日期失败：childrenId={}, time={}", check.getChildrenId(), check.getCheckInTime(), e);
//                continue;
//            }
//
//            String key = check.getChildrenId() + "_" + checkDateStr;
//            ChildrenCheckVO existCheck = studentLastCheckMap.get(key);
//            if (existCheck == null) {
//                studentLastCheckMap.put(key, check);
//                log.info("新增导出记录：key={}, status={}, 时间={}", key, check.getStatus(),
//                        (check.getStatus() == 2 || check.getStatus() == 6) ? check.getCheckOutTime() : check.getCheckInTime());
//            } else {
//                // 比较时间：保留更晚的记录（出园记录用checkOutTime）
//                try {
//                    String existTimeField = (existCheck.getStatus() == 2 || existCheck.getStatus() == 6) ? existCheck.getCheckOutTime() : existCheck.getCheckInTime();
//                    String currentTimeField = (check.getStatus() == 2 || check.getStatus() == 6) ? check.getCheckOutTime() : check.getCheckInTime();
//
//                    if (StringUtils.isBlank(existTimeField) || StringUtils.isBlank(currentTimeField)) {
//                        log.warn("时间字段为空：exist={}, current={}", existTimeField, currentTimeField);
//                        continue;
//                    }
//
//                    Date existTime = parseDate3(existTimeField, fullSdf, dateSdf);
//                    Date currentTime = parseDate3(currentTimeField, fullSdf, dateSdf);
//
//                    // 时间相同：优先保留出园记录（与查询接口一致）
//                    if (currentTime.after(existTime)) {
//                        studentLastCheckMap.put(key, check);
//                        log.info("更新导出记录：key={}, 原status={}→新status={}", key, existCheck.getStatus(), check.getStatus());
//                    } else if (currentTime.equals(existTime)) {
//                        boolean existIsCheckOut = existCheck.getStatus() == 2 || existCheck.getStatus() == 6;
//                        boolean currentIsCheckOut = check.getStatus() == 2 || check.getStatus() == 6;
//                        if (currentIsCheckOut && !existIsCheckOut) {
//                            studentLastCheckMap.put(key, check);
//                            log.info("时间相同，保留出园记录：key={}, 原status={}→新status={}", key, existCheck.getStatus(), check.getStatus());
//                        }
//                    }
//                } catch (ParseException e) {
//                    log.error("比较时间失败：key={}", key, e);
//                }
//            }
//        }
//
//        // 7. 计算应出勤日（与查询接口完全一致）
//        List<String> allDateList = DateUtil.getDay(beginAt, endAt);
//        List<String> workDayList = new ArrayList<>();
//        for (String dateStr : allDateList) {
//            try {
//                Date date = dateSdf.parse(dateStr);
//                Calendar cal = Calendar.getInstance();
//                cal.setTime(date);
//                int weekDay = cal.get(Calendar.DAY_OF_WEEK);
//                if (weekDay != Calendar.SUNDAY && weekDay != Calendar.SATURDAY
//                        && !HOLIDAYS_2025.contains(dateStr)) {
//                    workDayList.add(dateStr);
//                }
//            } catch (ParseException e) {
//                log.error("解析应出勤日失败：{}", dateStr, e);
//            }
//        }
//        int shouldAttendCount = workDayList.size();
//
//        // 8. 构建导出数据（与查询接口同步状态判定和统计逻辑）
//        List<Map<String, Object>> mapList = new ArrayList<>();
//        for (BabyNClassInfoVO baby : babyClassInfoList) {
//            Map<String, Object> studentMap = new HashMap<>();
//            studentMap.put("id", baby.getId());
//            studentMap.put("name", baby.getName()); // 对应Excel表头“学生名字”
//
//            // 统计字段（与查询接口字段名对齐：chiCount=迟到，zaoCount=早退）
//            int attend = 0;        // 出勤（1/2/5/6）
//            int leave = 0;         // 事假（4+healthy=0）
//            int sick = 0;          // 病假（4+healthy=1）
//            int chiCount = 0;      // 迟到（5）→ 对应查询接口的chiCount
//            int zaoCount = 0;      // 早退（6）→ 对应查询接口的zaoCount
//
//            // 遍历应出勤日，填充每天状态（与查询接口同步）
//            for (String workDay : workDayList) {
//                String key = baby.getId() + "_" + workDay;
//                ChildrenCheckVO lastCheck = studentLastCheckMap.get(key);
//                String dayResult = "缺勤"; // 默认缺勤
//
//                if (lastCheck != null) {
//                    int status = lastCheck.getStatus();
//                    // 出勤判定：与查询接口一致（仅1/2/5/6算出勤）
//                    boolean isAttend = Arrays.asList(1, 2, 5, 6).contains(status);
//                    if (isAttend) {
//                        attend++;
//                    }
//
//                    // 状态文本+统计计数（与查询接口完全同步）
//                    switch (status) {
//                        case 1: // 正常入园
//                            dayResult = "出勤";
//                            break;
//                        case 2: // 正常出园
//                            dayResult = "出勤"; // 与查询接口一致：正常出园显示“出勤”
//                            break;
//                        case 5: // 迟到
//                            dayResult = "迟到";
//                            chiCount++; // 对应查询接口的chiCount
//                            break;
//                        case 6: // 早退
//                            dayResult = "早退";
//                            zaoCount++; // 对应查询接口的zaoCount（关键：确保早退计数）
//                            break;
//                        case 4: // 请假
//                            if (lastCheck.getHealthyStatus() == 1) {
//                                dayResult = "病假";
//                                sick++;
//                            } else {
//                                dayResult = "事假";
//                                leave++;
//                            }
//                            break;
//                        default:
//                            dayResult = "缺勤";
//                            break;
//                    }
//                }
//
//                studentMap.put(workDay, dayResult); // 日期列：key=日期字符串
//            }
//
//            // 计算缺勤天数（与查询接口公式一致：应出勤 - 出勤 - 事假 - 病假）
//            int lackCount = shouldAttendCount - attend - leave - sick;
//            lackCount = Math.max(lackCount, 0); // 防止负数
//
//            // 统计字段：与查询接口字段名对齐（便于Excel映射）
//            studentMap.put("attendanceCount", attend); // 出勤天数
//            studentMap.put("leaveCount", leave);       // 事假天数
//            studentMap.put("sickCount", sick);        // 病假天数
//            studentMap.put("chiCount", chiCount);      // 迟到天数（对应查询接口chiCount）
//            studentMap.put("zaoCount", zaoCount);      // 早退天数（对应查询接口zaoCount）
//            studentMap.put("lackCount", lackCount);    // 缺勤天数
//
//            mapList.add(studentMap);
//        }
//
//        // 9. 构建Excel导出参数（确保表头与查询接口字段对齐）
//        List<ExcelDataVo> excelDataVoList = new ArrayList<>();
//        ExcelDataVo excelSheet = new ExcelDataVo();
//        excelSheet.setSheetName("班级学生考勤信息");
//        excelSheet.setSheetTopColHeaderTitle(
//                String.format("%s考勤报表（%s至%s）", classes.getName(), beginAt.split(" ")[0], endAt.split(" ")[0])
//        );
//
//        // 9.1 Excel表头配置（与map的key完全对应）
//        List<String> excelHeaderNames = new ArrayList<>(); // 表头显示名称
//        List<String> excelHeaderAttrs = new ArrayList<>(); // 表头对应map的key
//        // 基础列
//        excelHeaderNames.add("学生名字");
//        excelHeaderAttrs.add("name");
//        // 日期列（仅应出勤日）
//        excelHeaderNames.addAll(workDayList);
//        excelHeaderAttrs.addAll(workDayList);
//        // 统计列（与查询接口字段名一致）
//        excelHeaderNames.add("出勤天数");
//        excelHeaderNames.add("事假天数");
//        excelHeaderNames.add("病假天数");
//        excelHeaderNames.add("迟到天数");
//        excelHeaderNames.add("早退天数");
//        excelHeaderNames.add("缺勤天数");
//        excelHeaderAttrs.add("attendanceCount");
//        excelHeaderAttrs.add("leaveCount");
//        excelHeaderAttrs.add("sickCount");
//        excelHeaderAttrs.add("chiCount");
//        excelHeaderAttrs.add("zaoCount");
//        excelHeaderAttrs.add("lackCount");
//
//        // 9.2 封装Excel数据
//        excelSheet.setSheetTopColHeaderName(excelHeaderNames.toArray(new String[0]));
//        excelSheet.setSheetTopColHeaderAttribute(excelHeaderAttrs.toArray(new String[0]));
//        excelSheet.setSheetDataList(mapList);
//        excelDataVoList.add(excelSheet);
//
//        // 10. 执行Excel导出+OSS上传（原有逻辑不变，仅确保数据正确）
//        String localExcelPath = "";
//        File localExcelFile = null;
//        try {
//            // 导出Excel到本地
//            localExcelPath = new PackExcelSheetsDataUtil().packExcelSheetsData(response, "学生基础信息", excelDataVoList);
//            localExcelFile = new File(localExcelPath);
//            if (!localExcelFile.exists() || localExcelFile.length() == 0) {
//                log.error("Excel导出失败，文件为空，path:{}", localExcelPath);
//                return StatusResponse.error("99999", "Excel生成异常");
//            }
//
//            // 构建OSS文件名（原有逻辑不变）
//            String beginFullDate = yearMonthDaySdf.format(beginDate);
//            String endFullDate = yearMonthDaySdf.format(endDate);
//            String dateRange = (beginFullDate.substring(0, 4).equals(endFullDate.substring(0, 4)))
//                    ? beginFullDate + "-" + endFullDate.substring(5)
//                    : beginFullDate + "-" + endFullDate;
//            String ossFileName = String.format("%s%s考勤报表.xlsx", classes.getName(), dateRange);
//            String currentUploadDate = dateSdf.format(new Date());
//            String ossUploadPath = "teach/file/children_export_excel/" + currentUploadDate + "/" + ossFileName;
//
//            // OSS上传
//            String relativePath = ossProcessService.aliyunOssUpload(ossUploadPath, localExcelFile);
//            if (StringUtils.isBlank(relativePath)) {
//                log.error("OSS上传失败，path:{}", ossUploadPath);
//                return StatusResponse.error("99999", "文件上传失败");
//            }
//            String ossAccessUrl = IMG_SERVICE_PREFIX + relativePath;
//            return StatusResponse.success(ossAccessUrl);
//
//        } catch (Exception e) {
//            log.error("Excel导出异常", e);
//            return StatusResponse.error("99999", "导出失败：" + e.getMessage());
//        } finally {
//            // 清理临时文件
//            if (localExcelFile != null && localExcelFile.exists()) {
//                boolean deleteSuccess = localExcelFile.delete();
//                log.info("临时文件清理：path:{}, 结果:{}", localExcelPath, deleteSuccess);
//            }
//        }
//    }
    @PostMapping(value = "/exportClassChildrenCheckList")
    @ResponseBody
    @ApiOperation(value = "导出 - 班级考勤")
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "header", name = "ticket", value = "票据", required = true, dataType = "string"),
            @ApiImplicitParam(paramType = "query", name = "classId", value = "班级ID", required = true, dataType = "string"),
            @ApiImplicitParam(paramType = "query", name = "name", value = "学生名字", required = false, dataType = "string"),
            @ApiImplicitParam(paramType = "query", name = "beginAt", value = "开始时间yyyy-MM-dd", required = true, dataType = "string"),
            @ApiImplicitParam(paramType = "query", name = "endAt", value = "结束时间yyyy-MM-dd", required = true, dataType = "string"),
    })
    public StatusResponse exportClassChildrenCheckList(HttpServletResponse response,
                                                       @RequestHeader(TICKET) String ticket,
                                                       String classId, String name,
                                                       String beginAt, String endAt) {
        // 1. 基础参数校验
        if (StringUtils.isBlank(ticket)) {
            return StatusResponse.error("99999", "ticket不能为空");
        }
        if (StringUtils.isBlank(classId)) {
            return StatusResponse.error("99999", "班级ID不能为空");
        }
        if (StringUtils.isBlank(beginAt) || StringUtils.isBlank(endAt)) {
            return StatusResponse.error("99999", "开始日期和结束日期不能为空");
        }

        // 2. SSO获取用户信息
        StaffDTO staffDTO = ssoCallService.getStaffDTO(ticket);
        if (staffDTO == null || StringUtils.isBlank(staffDTO.getAreaId())) {
            log.error("获取用户园区信息失败，ticket:{}", ticket);
            return StatusResponse.error("99999", "用户身份无效，请重新登录");
        }

        // 3. 查询班级信息
        Classes classes = classesService.selectClassById(classId);
        if (classes == null || StringUtils.isBlank(classes.getName())) {
            log.error("班级不存在或班级名称为空，classId:{}", classId);
            return StatusResponse.error("99999", "无效的班级ID");
        }

        // 4. 统一日期格式（复用查询接口的parseDate3方法，确保格式一致）
        SimpleDateFormat fullSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        SimpleDateFormat dateSdf = new SimpleDateFormat("yyyy-MM-dd");
        SimpleDateFormat yearMonthDaySdf = new SimpleDateFormat("yyyy年MM月dd日");
        fullSdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
        dateSdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
        yearMonthDaySdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));

        Date beginDate = null;
        Date endDate = null;
        try {
            beginDate = parseDate3(beginAt, fullSdf, dateSdf);
            endDate = parseDate3(endAt, fullSdf, dateSdf);
            beginAt = dateSdf.format(beginDate) + " 00:00:00";
            endAt = dateSdf.format(endDate) + " 23:59:59";
        } catch (ParseException e) {
            log.error("日期格式解析失败，beginAt:{}, endAt:{}", beginAt, endAt, e);
            return StatusResponse.error("99999", "日期格式错误，请传入yyyy-MM-dd或yyyy-MM-dd HH:mm:ss格式");
        }

        // 5. 获取班级学生列表
        List<BabyNClassInfoVO> babyClassInfoList = childrenService.getChildrenClassInfoForCrm(
                staffDTO.getAreaId(), name, classId);
        if (CollectionUtils.isEmpty(babyClassInfoList)) {
            log.info("班级无学生数据，classId:{}, areaId:{}", classId, staffDTO.getAreaId());
            return StatusResponse.success("班级无学生数据，无需导出");
        }

        // 6. 获取考勤记录 + 预处理（适配新状态字段：按“学生ID_日期”分组，保留当天最后一条记录）
        List<String> childrenIds = babyClassInfoList.stream()
                .map(BabyNClassInfoVO::getId)
                .collect(Collectors.toList());
        List<ChildrenCheckVO> childrenCheckList = dashboardService.getChildrenClassCheck(childrenIds, beginAt, endAt);
        log.info("查询到的考勤记录共{}条", childrenCheckList.size());

        // 6.1 预处理核心逻辑：适配status和leaveStatus拆分
        Map<String, ChildrenCheckVO> studentLastCheckMap = new HashMap<>();
        for (ChildrenCheckVO check : childrenCheckList) {
            if (check == null || StringUtils.isBlank(check.getChildrenId())) {
                continue; // 过滤无效记录
            }

            // 解析打卡日期（统一为yyyy-MM-dd，用于分组）
            String checkDateStr;
            try {
                // 关键：优先用离园时间（leaveStatus≠0），无离园用入园时间（与查询接口同步）
                String timeField = check.getLeaveStatus() != 0 ? check.getCheckOutTime() : check.getCheckInTime();
                if (StringUtils.isBlank(timeField)) {
                    log.warn("记录时间为空：childrenId={}, status={}, leaveStatus={}",
                            check.getChildrenId(), check.getStatus(), check.getLeaveStatus());
                    continue;
                }
                Date checkDate = parseDate3(timeField, fullSdf, dateSdf);
                checkDateStr = dateSdf.format(checkDate);
            } catch (ParseException e) {
                log.error("解析记录日期失败：childrenId={}, checkInTime={}, checkOutTime={}",
                        check.getChildrenId(), check.getCheckInTime(), check.getCheckOutTime(), e);
                continue;
            }

            String key = check.getChildrenId() + "_" + checkDateStr;
            ChildrenCheckVO existCheck = studentLastCheckMap.get(key);
            if (existCheck == null) {
                studentLastCheckMap.put(key, check);
                log.info("新增导出记录：key={}, status={}, leaveStatus={}, 时间={}",
                        key, check.getStatus(), check.getLeaveStatus(),
                        check.getLeaveStatus() != 0 ? check.getCheckOutTime() : check.getCheckInTime());
            } else {
                // 比较时间：保留更晚的记录（优先用离园时间比较）
                try {
                    // 原记录时间字段：有离园用离园时间
                    String existTimeField = existCheck.getLeaveStatus() != 0 ? existCheck.getCheckOutTime() : existCheck.getCheckInTime();
                    // 当前记录时间字段：有离园用离园时间
                    String currentTimeField = check.getLeaveStatus() != 0 ? check.getCheckOutTime() : check.getCheckInTime();

                    if (StringUtils.isBlank(existTimeField) || StringUtils.isBlank(currentTimeField)) {
                        log.warn("时间字段为空：exist={}, current={}", existTimeField, currentTimeField);
                        continue;
                    }

                    Date existTime = parseDate3(existTimeField, fullSdf, dateSdf);
                    Date currentTime = parseDate3(currentTimeField, fullSdf, dateSdf);

                    // 时间相同：优先保留有离园记录的（与查询接口逻辑一致）
                    if (currentTime.after(existTime)) {
                        studentLastCheckMap.put(key, check);
                        log.info("更新导出记录：key={}, 原状态({},{}), 新状态({},{}), 原时间={}, 新时间={}",
                                key, existCheck.getStatus(), existCheck.getLeaveStatus(),
                                check.getStatus(), check.getLeaveStatus(),
                                existTimeField, currentTimeField);
                    } else if (currentTime.equals(existTime)) {
                        boolean existHasLeave = existCheck.getLeaveStatus() != 0;
                        boolean currentHasLeave = check.getLeaveStatus() != 0;
                        if (currentHasLeave && !existHasLeave) {
                            studentLastCheckMap.put(key, check);
                            log.info("时间相同，保留出园记录：key={}, 原状态({},{}), 新状态({},{}),",
                                    key, existCheck.getStatus(), existCheck.getLeaveStatus(),
                                    check.getStatus(), check.getLeaveStatus());
                        }
                    }
                } catch (ParseException e) {
                    log.error("比较时间失败：key={}", key, e);
                }
            }
        }

        // 7. 计算应出勤日（与查询接口完全一致：排除周末和节假日）
        List<String> allDateList = DateUtil.getDay(beginAt, endAt);
        List<String> workDayList = new ArrayList<>();
        for (String dateStr : allDateList) {
            try {
                Date date = dateSdf.parse(dateStr);
                Calendar cal = Calendar.getInstance();
                cal.setTime(date);
                int weekDay = cal.get(Calendar.DAY_OF_WEEK);
                // 排除周六（7）、周日（1）和节假日
                if (weekDay != Calendar.SUNDAY && weekDay != Calendar.SATURDAY
                        && !HOLIDAYS_2025.contains(dateStr)) {
                    workDayList.add(dateStr);
                }
            } catch (ParseException e) {
                log.error("解析应出勤日失败：{}", dateStr, e);
            }
        }
        int shouldAttendCount = workDayList.size();

        // 8. 构建导出数据（核心：适配新状态字段的判定和统计）
        List<Map<String, Object>> mapList = new ArrayList<>();
        for (BabyNClassInfoVO baby : babyClassInfoList) {
            Map<String, Object> studentMap = new HashMap<>();
            studentMap.put("id", baby.getId());
            studentMap.put("name", baby.getName()); // 学生名字（Excel基础列）

            // 统计字段初始化（与查询接口字段名完全对齐）
            int attend = 0;        // 出勤天数（有入园且非请假）
            int leave = 0;         // 事假天数（status=4且healthy=0）
            int sick = 0;          // 病假天数（status=4且healthy=1）
            int chiCount = 0;      // 迟到天数（status=5）
            int zaoCount = 0;      // 早退天数（leaveStatus=6）

            // 遍历应出勤日，填充每日状态和统计数据
            for (String workDay : workDayList) {
                String key = baby.getId() + "_" + workDay;
                ChildrenCheckVO lastCheck = studentLastCheckMap.get(key);
                String dayResult = "缺勤"; // 默认：无记录=缺勤

                if (lastCheck != null) {
                    int status = lastCheck.getStatus();          // 入园状态（0=缺席,1=正常,3=拒绝,4=请假,5=迟到）
                    int leaveStatus = lastCheck.getLeaveStatus(); // 离园状态（0=未离园,2=正常,6=早退）
                    int healthyStatus = lastCheck.getHealthyStatus(); // 健康状态（1=病假）

                    // 核心1：出勤判定（与查询接口一致：非请假+有入园就算出勤）
                    boolean isAttend = Arrays.asList(1, 3, 5).contains(status) && status != 4;
                    if (isAttend) {
                        attend++;
                    }
                    log.info("导出统计：日期={}, 学生ID={}, 入园状态={}, 离园状态={}, 是否出勤={}",
                            workDay, baby.getId(), status, leaveStatus, isAttend);

                    // 核心2：状态文本+细分统计（结合入园和离园状态）
                    if (status == 4) {
                        // 请假状态（忽略离园状态）
                        if (healthyStatus == 1) {
                            dayResult = "病假";
                            sick++;
                        } else {
                            dayResult = "事假";
                            leave++;
                        }
                    } else if (status == 3) {
                        // 拒绝入园（单独状态，不算出勤）
                        dayResult = "拒绝入园";
                    } else if (status == 5) {
                        // 迟到（无论是否出园，都算迟到）
                        dayResult = leaveStatus != 0 ? "迟到+出园" : "迟到";
                        chiCount++; // 迟到计数（与查询接口chiCount对齐）
                    } else if (status == 1) {
                        // 正常入园（根据离园状态细分）
                        if (leaveStatus == 2) {
                            dayResult = "正常出勤"; // 正常出园=正常出勤
                        } else if (leaveStatus == 6) {
                            dayResult = "早退";     // 早退=出勤+早退计数
                            zaoCount++; // 早退计数（与查询接口zaoCount对齐）
                        } else {
                            dayResult = "已入园";   // 未出园=出勤
                        }
                    }
                }

                // 日期列：key=日期字符串（如"2025-09-01"），value=当日状态（如"早退"）
                studentMap.put(workDay, dayResult);
            }

            // 核心3：计算缺勤天数（公式与查询接口完全一致）
            int lackCount = shouldAttendCount - attend - leave - sick;
            lackCount = Math.max(lackCount, 0); // 防止负数（异常场景）

            // 填充统计列（字段名与查询接口完全对齐，确保Excel表头映射正确）
            studentMap.put("attendanceCount", attend);       // 出勤天数
            studentMap.put("leaveCount", leave);             // 事假天数
            studentMap.put("sickCount", sick);              // 病假天数
            studentMap.put("chiCount", chiCount);            // 迟到天数
            studentMap.put("zaoCount", zaoCount);            // 早退天数
            studentMap.put("lackCount", lackCount);          // 缺勤天数

            mapList.add(studentMap);
        }

        // 9. 构建Excel导出配置（确保表头与数据字段对齐）
        List<ExcelDataVo> excelDataVoList = new ArrayList<>();
        ExcelDataVo excelSheet = new ExcelDataVo();
        excelSheet.setSheetName("班级学生考勤信息");
        // Excel标题：包含班级名和日期范围（如“大一班考勤报表（2025-09-01至2025-09-30）”）
        excelSheet.setSheetTopColHeaderTitle(
                String.format("%s考勤报表（%s至%s）",
                        classes.getName(),
                        beginAt.split(" ")[0],
                        endAt.split(" ")[0])
        );

        // 9.1 Excel表头配置（顺序：基础列→日期列→统计列）
        List<String> excelHeaderNames = new ArrayList<>(); // 表头显示名称
        List<String> excelHeaderAttrs = new ArrayList<>(); // 表头对应map的key
        // 基础列：学生名字
        excelHeaderNames.add("学生名字");
        excelHeaderAttrs.add("name");
        // 日期列：仅应出勤日（按时间顺序排列）
        excelHeaderNames.addAll(workDayList);
        excelHeaderAttrs.addAll(workDayList);
        // 统计列：与查询接口字段名一致，确保用户理解
        excelHeaderNames.add("出勤天数");
        excelHeaderNames.add("事假天数");
        excelHeaderNames.add("病假天数");
        excelHeaderNames.add("迟到天数");
        excelHeaderNames.add("早退天数");
        excelHeaderNames.add("缺勤天数");
        excelHeaderAttrs.add("attendanceCount");
        excelHeaderAttrs.add("leaveCount");
        excelHeaderAttrs.add("sickCount");
        excelHeaderAttrs.add("chiCount");
        excelHeaderAttrs.add("zaoCount");
        excelHeaderAttrs.add("lackCount");

        // 9.2 封装Excel数据（与表头配置映射）
        excelSheet.setSheetTopColHeaderName(excelHeaderNames.toArray(new String[0]));
        excelSheet.setSheetTopColHeaderAttribute(excelHeaderAttrs.toArray(new String[0]));
        excelSheet.setSheetDataList(mapList);
        excelDataVoList.add(excelSheet);
        excelDataVoList.add(excelSheet);
        // 10. 执行Excel导出+OSS上传（原有逻辑不变，仅确保数据正确）
        String localExcelPath = "";
        File localExcelFile = null;
        try {
            // 导出Excel到本地
            localExcelPath = new PackExcelSheetsDataUtil().packExcelSheetsData(response, "学生基础信息", excelDataVoList);
            localExcelFile = new File(localExcelPath);
            if (!localExcelFile.exists() || localExcelFile.length() == 0) {
                log.error("Excel导出失败，文件为空，path:{}", localExcelPath);
                return StatusResponse.error("99999", "Excel生成异常");
            }

            // 构建OSS文件名（原有逻辑不变）
            String beginFullDate = yearMonthDaySdf.format(beginDate);
            String endFullDate = yearMonthDaySdf.format(endDate);
            String dateRange = (beginFullDate.substring(0, 4).equals(endFullDate.substring(0, 4)))
                    ? beginFullDate + "-" + endFullDate.substring(5)
                    : beginFullDate + "-" + endFullDate;
            String ossFileName = String.format("%s%s考勤报表.xls", classes.getName(), dateRange);
            String currentUploadDate = dateSdf.format(new Date());
            String ossUploadPath = "teach/file/children_export_excel/" + currentUploadDate + "/" + ossFileName;

            // OSS上传
            String relativePath = ossProcessService.aliyunOssUpload(ossUploadPath, localExcelFile);
            if (StringUtils.isBlank(relativePath)) {
                log.error("OSS上传失败，path:{}", ossUploadPath);
                return StatusResponse.error("99999", "文件上传失败");
            }
            String ossAccessUrl = IMG_SERVICE_PREFIX + relativePath;
            return StatusResponse.success(ossAccessUrl);

        } catch (Exception e) {
            log.error("Excel导出异常", e);
            return StatusResponse.error("99999", "导出失败：" + e.getMessage());
        } finally {
            // 清理临时文件
            if (localExcelFile != null && localExcelFile.exists()) {
                boolean deleteSuccess = localExcelFile.delete();
                log.info("临时文件清理：path:{}, 结果:{}", localExcelPath, deleteSuccess);
            }
        }
    }









    /**
     * 班级考勤
     */
    @PostMapping(value = "/exportClassContract")
    @ResponseBody
    @ApiOperation(value = "导出 - 班级合同")
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "header", name = "ticket", value = "票据", required = true, dataType = "string"),
            @ApiImplicitParam(paramType = "query", name = "classId", value = "班级ID", required = true, dataType = "string"),
    })
    public StatusResponse exportClassContract( @RequestHeader(TICKET) String ticket, String classId,HttpServletResponse response) {

        StaffDTO staffDTO  = ssoCallService.getStaffDTO(ticket);
        Classes classes  = classesService.selectClassById(classId);
        //班级学生信息
        List<BabyNClassInfoVO> babyClassInfoList = childrenService.getChildrenClassInfoForCrm(staffDTO.getAreaId(),null,classId);

        List<String> clienteleIds = babyClassInfoList.stream().map(k -> k.getCrmClienteleId()).collect(Collectors.toList());
        String[] clienteleIdString = new String[clienteleIds.size()];
        String[] clienteleIdsString = clienteleIds.toArray(clienteleIdString);
        //学生合同列表
        List<ContractVo>  contractList = nurseryInfoRecordService.clientelesIdByContractList(Arrays.toString(clienteleIdsString));
        List<String> childrenIds = babyClassInfoList.stream().map(k -> k.getId()).collect(Collectors.toList());
        String[] childrenIdString = new String[childrenIds.size()];
        String[] childrenIdsString = childrenIds.toArray(childrenIdString);
        //学生消课，请假，双倍记录
        List<ChildrenNurseryInfoRecord> nurseryInfoRecordList = nurseryInfoRecordService.findByChildrenIdIn(childrenIdsString);

        List<Map<String, Object>> mapList = new ArrayList<>();

        for (int i = 0; i < babyClassInfoList.size(); i++) {

            for (int j = 0; j < contractList.size(); j++) {
                if (StringUtils.equals(babyClassInfoList.get(i).getCrmClienteleId(),contractList.get(j).getClienteleId())){
                    String beginDate = "";
                    if (null != contractList.get(j).getBeginDate()){
                        beginDate =  DateUtil.formatDayToStr(contractList.get(j).getBeginDate());
                    }
                    String endDate = "";
                    if (null != contractList.get(j).getEndDate()){
                        endDate =  DateUtil.formatDayToStr(contractList.get(j).getEndDate());
                    }
                    Map m = new HashMap();
                    m.put("name",babyClassInfoList.get(i).getName());
                    m.put("className",classes.getName());
                    m.put("beginDate",beginDate);
                    m.put("endDate",endDate);
                    m.put("contractName",contractList.get(j).getName());
                    m.put("contractStatus",contractList.get(j).getStatusVal());
                    //正常记录
                    int q1= 0;
                    //请假记录
                    int q2= 0;
                    //入户记录
                    int q3= 0;
                    for (int k = 0; k < nurseryInfoRecordList.size(); k++) {
                        if (StringUtils.equals(babyClassInfoList.get(i).getId(),nurseryInfoRecordList.get(k).getChildrenId())){
                            if (nurseryInfoRecordList.get(k).getType() == 0){
                                q1++;
                            }
                            if (nurseryInfoRecordList.get(k).getType() == 1){
                                q2 = q2 + nurseryInfoRecordList.get(k).getEffectDay();
                            }
                            if (nurseryInfoRecordList.get(k).getType() == 2){
                                q3 = q3 + nurseryInfoRecordList.get(k).getEffectDay();
                            }
                        }
                    }
                    m.put("checkCount",q1);
                    m.put("leaveCount",q2);
                    m.put("registerCount",q3);
                    mapList.add(m);
                }
            }
        }
        // 封装需要导出的数据
        List<ExcelDataVo> excelDataVoList = new ArrayList<>();

        List<String> dateList1 = new ArrayList<>();
        dateList1.add("学生名字");
        dateList1.add("班级名称");
        dateList1.add("合同名称");
        dateList1.add("合同状态");
        dateList1.add("开始时间");
        dateList1.add("结束时间");
        dateList1.add("考勤统计");
        dateList1.add("请假统计");
        dateList1.add("入户统计");

        List<String> dateList2 = new ArrayList<>();
        dateList2.add("name");
        dateList2.add("className");
        dateList2.add("contractName");
        dateList2.add("contractStatus");
        dateList2.add("beginDate");
        dateList2.add("endDate");
        dateList2.add("checkCount");
        dateList2.add("leaveCount");
        dateList2.add("registerCount");
        // 封装学生信息
        ExcelDataVo excelDataSheetOne = new ExcelDataVo();
        excelDataSheetOne.setSheetName("班级学生考勤信息");
        excelDataSheetOne.setSheetTopColHeaderTitle("班级学生考勤信息");
        excelDataSheetOne.setSheetTopColHeaderName(dateList1.toArray(new String[dateList1.size()]));
        excelDataSheetOne.setSheetTopColHeaderAttribute(dateList2.toArray(new String[dateList2.size()]));
        excelDataSheetOne.setSheetDataList(mapList);
        excelDataVoList.add(excelDataSheetOne);


        List<String> babyList = new ArrayList<>();
        babyList.add("学生名字");
        babyList.add("发生时间");
        babyList.add("类型");
        babyList.add("合同时间");
        babyList.add("变更时间");
        babyList.add("操作人");
        babyList.add("备注");

        List<String> babyList_1 = new ArrayList<>();
        babyList_1.add("name");
        babyList_1.add("created");
        babyList_1.add("type");
        babyList_1.add("nurseryOriginalEndDate");
        babyList_1.add("nurseryEndDate");
        babyList_1.add("staffName");
        babyList_1.add("remark");
        //学生信息
        for (int i = 0; i < babyClassInfoList.size(); i++) {
            List<Map<String, Object>> childrenList = new ArrayList<>();

            for (int j = 0; j < nurseryInfoRecordList.size(); j++) {
                if (StringUtils.equals(babyClassInfoList.get(i).getId(),nurseryInfoRecordList.get(j).getChildrenId())){
                    Map<String, Object> m = new HashMap<>();
                    m.put("name",babyClassInfoList.get(i).getName());
                    m.put("created",DateUtil.formatDayToStr(nurseryInfoRecordList.get(j).getCreated()));
                    m.put("type",getNurseryInfoRecordType(nurseryInfoRecordList.get(j).getType()));
                    String nurseryOriginalEndDate = "";
                    String nurseryEndDate = "";
                    if (nurseryInfoRecordList.get(j).getType()>0){
                        nurseryOriginalEndDate = DateUtil.formatDayToStr(nurseryInfoRecordList.get(j).getNurseryOriginalEndDate());
                        nurseryEndDate = DateUtil.formatDayToStr(nurseryInfoRecordList.get(j).getNurseryEndDate());
                    }
                    m.put("nurseryOriginalEndDate",nurseryOriginalEndDate);
                    m.put("nurseryEndDate",nurseryEndDate);
                    m.put("staffName",nurseryInfoRecordList.get(j).getCreatedName());
                    m.put("remark",nurseryInfoRecordList.get(j).getRemark());
                    childrenList.add(m);
                }
            }
            // 封装学生信息
            ExcelDataVo excelDataSheetOne_1 = new ExcelDataVo();
            excelDataSheetOne_1.setSheetName(babyClassInfoList.get(i).getName());
            excelDataSheetOne_1.setSheetTopColHeaderTitle(babyClassInfoList.get(i).getName()+"（合同时间只有：请假入户记录才显示）");
            excelDataSheetOne_1.setSheetTopColHeaderName(babyList.toArray(new String[babyList.size()]));
            excelDataSheetOne_1.setSheetTopColHeaderAttribute(babyList_1.toArray(new String[babyList_1.size()]));
            excelDataSheetOne_1.setSheetDataList(childrenList);
            excelDataVoList.add(excelDataSheetOne_1);
        }



        String url= "";
        // 导出数据
        try {
            url  = new PackExcelSheetsDataUtil().packExcelSheetsData(response, "学生基础信息", excelDataVoList);
        } catch (Exception e) {
            throw new Exception("导出异常!");
        }finally {
            File outputFile = new File(url);
            String uploadPath = "teach/file/children_export_excel/" + DateTime.now().toString("yyyy-MM") + "/"+classes.getName()+"考勤信息报表.xlsx";
            String relativePath = ossProcessService.aliyunOssUpload(uploadPath, outputFile);
            String uploadedResult = IMG_SERVICE_PREFIX + relativePath;
            return StatusResponse.success(uploadedResult);
        }
    }

    String getNurseryInfoRecordType(Integer type){
        if (type ==0){
            return "打卡";
        }
        if (type ==1){
            return "请假";
        }
        if (type ==2){
            return "入户";
        }
        return null;
    }

    @ApiOperation(value = "考勤管理列表",httpMethod = "POST")
    @RequestMapping("/getClassCheckList")
    public StatusResponse getClassCheckList(@RequestHeader(TICKET) String ticket, ClassesQuery classesQuery) {
        StaffDTO baseInfo = cache.getStaff(ticket);
        classesQuery.setAreaId(baseInfo.getAreaId());
        classesQuery.setDate(DateUtil.millisToDateTime(DateUtil.startOfDayOfMilli(System.currentTimeMillis()), "yyyy-MM-dd"));
        Page<ChildrenClassCheckVO> classCheckList = classesService.getClassCheckList(classesQuery);
        return StatusResponse.success(classCheckList);
    }

    @ApiOperation(value = "考勤管理详情",httpMethod = "POST")
    @RequestMapping("/getClassCheckDetail")
    public StatusResponse getClassCheckDetail(ClassesQuery classesQuery) {

        Page<ChildrenClassCheckVO> classCheckList = classesService.getClassCheckDetail(classesQuery);
        return StatusResponse.success(classCheckList);
    }

    @ApiOperation(value = "考勤管理详情",httpMethod = "GET")
    @GetMapping("/exportClassCheckDetail")
    public void exportClassCheckDetail(HttpServletResponse response,ClassesQuery classesQuery) throws IOException {
        classesQuery.setPageNum(0);
        classesQuery.setPageSize(9999);
        Page<ChildrenClassCheckVO> classCheckList = classesService.getClassCheckDetail(classesQuery);
        ExcelUtil<ChildrenClassCheckVO> excelUtil = new ExcelUtil<ChildrenClassCheckVO>(ChildrenClassCheckVO.class);
        excelUtil.exportExcel(response,classCheckList.getContent(),"托班考勤导出","托班考勤导出");
    }



    @ApiOperation(value = "考勤修改入园、离园时间",httpMethod = "POST")
    @PostMapping("/editChildrenCheckTime")
    public StatusResponse editChildrenCheckTime(@RequestHeader(TICKET) String ticket,@RequestBody ChildrenClassCheckVO childrenClassCheckVO) {
        StaffDTO baseInfo = cache.getStaff(ticket);
        childrenClassCheckVO.setAreaId(baseInfo.getAreaId());
        classesService.editChildrenCheckTime(childrenClassCheckVO);

        return StatusResponse.success();
    }


}
